
#include "qtch/db/postgresql.h"
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdarg.h>
#include "qtch/log.h"
#include "qtch/util.h"
#include "qtch/config.h"

namespace qtch {

static Logger::ptr logger = QTCH_LOG_NAME("system");
static qtch::ConfigVar<std::map<std::string,std::map<std::string,std::string> > >::ptr g_postgresql_dbs 
        = qtch::Config::LookUp("db.postgresql",std::map<std::string,std::map<std::string,std::string> >(),"postgresql dbs");


bool postgresql_timestamp_to_time_t(const char* timestamp, time_t& ts) {
    int year, month, day, hour, min, sec;
    if(!timestamp){
        ts = 0;
        return false;
    }
    int rt = sscanf(timestamp, "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour,
                    &min, &sec);
    if (rt != 6) {
        ts = 0;
        return false;
    }
    struct tm tm;
    localtime_r(&ts, &tm);
    tm.tm_year = year - 1900;
    tm.tm_mon = month - 1;
    tm.tm_mday = day;
    tm.tm_hour = hour;
    tm.tm_min = min;
    tm.tm_sec = sec;
    ts = mktime(&tm);
    if (ts < 0) {
        ts = 0;
        return false;
    }
    return true;
}

bool postgresql_timestamp_to_time_t(const std::string& timestamp, time_t& ts) {
    return postgresql_timestamp_to_time_t(timestamp.c_str(), ts);
}

bool time_to_postgresql_timestamp(time_t time, std::string& timestamp) {
    struct tm tm;
    localtime_r(&time, &tm);
    char times[80];
    sprintf(times, "%d-%d-%d %d:%d:%d", tm.tm_year + 1900, tm.tm_mon + 1,
            tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
    timestamp = std::string(times);
    return true;
}

PostgresqlRes::PostgresqlRes(PGresult* res){
    m_curIndex = -1;
    m_data.reset(res,PQclear);
}

int PostgresqlRes::getErrno() const {
    return PQresultStatus(m_data.get());
}

const std::string PostgresqlRes::getErrStr() const {
    char* resultError = PQresultErrorMessage(m_data.get());
    std::string str(resultError);
    return str;
}

int PostgresqlRes::getDataCount() {
    return PQntuples(m_data.get());
}

int PostgresqlRes::getColumnCount() {
    return PQnfields(m_data.get());
}

int PostgresqlRes::getColumnBytes(int idx) {
    return PQgetlength(m_data.get(), m_curIndex, idx);
}

int PostgresqlRes::getColumnType(int idx) {
    return 0;
}

int PostgresqlRes::getColumnIdx(const std::string& columnName) {
    return PQfnumber(m_data.get(), columnName.c_str());
}

int PostgresqlRes::getColumnIdx(const char* columnName) {
    return PQfnumber(m_data.get(), columnName);
}

std::string PostgresqlRes::getColumnName(int idx) {
    char* columnName = PQfname(m_data.get(), idx);
    std::string resultStr = std::string(columnName);
    return resultStr;
}

bool PostgresqlRes::isNull(int idx) {
    return PQgetisnull(m_data.get(), m_curIndex, idx);
}

int8_t PostgresqlRes::getInt8(int idx) {
    return getInt64(idx);
}

uint8_t PostgresqlRes::getUint8(int idx) {
    return getInt64(idx);
}

int16_t PostgresqlRes::getInt16(int idx) {
    return getInt64(idx);
}

uint16_t PostgresqlRes::getUint16(int idx) {
    return getInt64(idx);
}

int32_t PostgresqlRes::getInt32(int idx) {
    return getInt64(idx);
}

uint32_t PostgresqlRes::getUint32(int idx) {
    return getInt64(idx);
}

int64_t PostgresqlRes::getInt64(int idx) {
    char* value = PQgetvalue(m_data.get(), m_curIndex, idx);
    return TypeUtil::Atoi(value);
}

uint64_t PostgresqlRes::getUint64(int idx) {
    return getInt64(idx);
}

int8_t PostgresqlRes::getInt8(const std::string& name) {
    return getInt64(name);
}

uint8_t PostgresqlRes::getUint8(const std::string& name) {
    return getInt64(name);
}

int16_t PostgresqlRes::getInt16(const std::string& name) {
    return getInt64(name);
}

uint16_t PostgresqlRes::getUint16(const std::string& name) {
    return getInt64(name);
}

int32_t PostgresqlRes::getInt32(const std::string& name) {
    return getInt64(name);
}

uint32_t PostgresqlRes::getUint32(const std::string& name) {
    return getInt64(name);
}

int64_t PostgresqlRes::getInt64(const std::string& name) {
    int idx = getColumnIdx(name);
    return getInt64(idx);
}

uint64_t PostgresqlRes::getUint64(const std::string& name) {
    return getInt64(name);
}

float PostgresqlRes::getFloat(int idx) {
    return getDouble(idx);
}

double PostgresqlRes::getDouble(int idx) {
    char* value = PQgetvalue(m_data.get(), m_curIndex, idx);
    return TypeUtil::Atof(value);
}

float PostgresqlRes::getFloat(const std::string& name) {
    return getDouble(name);
}

double PostgresqlRes::getDouble(const std::string& name) {
    int idx = getColumnIdx(name);
    return getDouble(idx);
}

std::string PostgresqlRes::getString(int idx) {
    char* value = PQgetvalue(m_data.get(), m_curIndex, idx);
    return std::string(value, getColumnBytes(idx));
}

std::string PostgresqlRes::getBlob(int idx) {
    char* value = PQgetvalue(m_data.get(), m_curIndex, idx);
    return std::string(value, getColumnBytes(idx));
}

std::string PostgresqlRes::getString(const std::string& name) {
    int idx = getColumnIdx(name);
    return getString(idx);
}

std::string PostgresqlRes::getBlob(const std::string& name) {
    int idx = getColumnIdx(name);
    return getBlob(idx);
}

time_t PostgresqlRes::getTime(int idx) {
    char* value = PQgetvalue(m_data.get(), m_curIndex, idx);
    time_t ts;
    if (!postgresql_timestamp_to_time_t(value, ts)) {
        QTCH_LOG_DEBUG(logger)
            << "postgresql_timestamp_to_time_t faile value=" << value;
    }
    return ts;
}

time_t PostgresqlRes::getTime(const std::string& name) {
    int idx = getColumnIdx(name);
    return getTime(idx);
}

bool PostgresqlRes::isNull(const std::string& name) {
    int idx = getColumnIdx(name);
    return isNull(idx);
}

bool PostgresqlRes::next() {
    ++m_curIndex;
    if (m_curIndex < getDataCount()) {
        return true;
    }
    return false;
}

PostgresqlStmt::ptr PostgresqlStmt::Create(PostgreSQL::ptr db){
    PostgresqlStmt::ptr rt = PostgresqlStmt::ptr(new PostgresqlStmt(db));
    return rt;
}

PostgresqlStmt::ptr PostgresqlStmt::Create(PostgreSQL::ptr db,const std::string & stmt) {
    PostgresqlStmt::ptr rt = PostgresqlStmt::ptr(new PostgresqlStmt(db,stmt));
    return rt;
}

PostgresqlStmt::PostgresqlStmt(PostgreSQL::ptr db) {
    m_postgresql = db;
    m_paramNum = 0;
}

PostgresqlStmt::PostgresqlStmt(PostgreSQL::ptr db,const std::string & stmt) {
    m_postgresql = db;
    setSqlSentence(stmt);
    m_paramNum = 0;
}


PostgresqlStmt::~PostgresqlStmt() {
    for (size_t i = 0; i < m_paramValues.size(); ++i) {
        if (m_paramValues[i] != nullptr) {
            free(m_paramValues[i]);
        }
    }
}

void PostgresqlStmt::checkParamMemory(int idx) {
    if(m_paramNum<idx+1){
        m_paramNum = idx+1;
    }
    for (size_t i = m_paramValues.size(); i <=(size_t)idx; i++) {
        m_paramValues.push_back(nullptr);
    }
    for (size_t i = m_paramLengths.size(); i <=(size_t)idx; i++) {
        m_paramLengths.push_back(0);
    }
    if (m_paramValues[idx] != nullptr) {
        free(m_paramValues[idx]);
    }
    m_paramValues[idx] = nullptr;
    m_paramLengths[idx] = 0;
}

int PostgresqlStmt::bindInt8(int idx, const int8_t& value) {
#define XX(idx, value)                                         \
    checkParamMemory(idx);                                     \
    m_paramValues[idx] = (char*)malloc(sizeof(value));                \
    m_paramLengths[idx] = sizeof(value);                       \
    std::string sValue = TypeUtil::ItoA((int64_t)value);       \
    memcpy(m_paramValues[idx], sValue.c_str(), sizeof(value)); \
    return 0;
    XX(idx, value);
}

int PostgresqlStmt::bindUint8(int idx, const uint8_t& value) {
    XX(idx, value);
}

int PostgresqlStmt::bindInt16(int idx, const int16_t& value) {
    XX(idx, value);
}

int PostgresqlStmt::bindUint16(int idx, const uint16_t& value) {
    XX(idx, value);
}

int PostgresqlStmt::bindInt32(int idx, const int32_t& value) {
    XX(idx, value);
}

int PostgresqlStmt::bindUint32(int idx, const uint32_t& value) {
    XX(idx, value);
}

int PostgresqlStmt::bindInt64(int idx, const int64_t& value) {
    XX(idx, value);
#undef XX
}

int PostgresqlStmt::bindUint64(int idx, const uint64_t& value) {
    checkParamMemory(idx);
    m_paramValues[idx] = (char*)malloc(sizeof(value));
    m_paramLengths[idx] = sizeof(value);
    std::string sValue = TypeUtil::ItoA((uint64_t)value);
    memcpy(m_paramValues[idx], sValue.c_str(), sizeof(value));
    return 0;
}

int PostgresqlStmt::bindFloat(int idx, const float& value) {
#define XX(idx, value)                                         \
    checkParamMemory(idx);                                     \
    m_paramValues[idx] = (char*)malloc(sizeof(value));                \
    m_paramLengths[idx] = sizeof(value);                       \
    std::string sValue = TypeUtil::Itof(value);                \
    memcpy(m_paramValues[idx], sValue.c_str(), sizeof(value)); \
    return 0;

    XX(idx, value);
}

int PostgresqlStmt::bindDouble(int idx, const double& value) {
    XX(idx, value);

#undef XX
}

int PostgresqlStmt::bindString(int idx, const char* value) {
    checkParamMemory(idx);
    int len = strlen(value);
    m_paramValues[idx] = (char*)malloc(len + 1);
    m_paramLengths[idx] = len + 1;
    memcpy(m_paramValues[idx], value, len + 1);
    return 0;
}

int PostgresqlStmt::bindString(int idx, const std::string& value) {
    return bindString(idx, value.c_str());
}

int PostgresqlStmt::bindBlob(int idx, const char* value, int64_t size) {
    checkParamMemory(idx);
    m_paramValues[idx] = (char*)malloc(size);
    m_paramLengths[idx] = size;
    memcpy(m_paramValues[idx], value, size);
    return 0;
}

int PostgresqlStmt::bindBlob(int idx, const std::string& value) {
    return bindBlob(idx, value.c_str(), value.length());
}

int PostgresqlStmt::bindTime(int idx, const time_t& value) {
    checkParamMemory(idx);
    std::string tempstamp = "";
    time_to_postgresql_timestamp(value, tempstamp);
    int len = tempstamp.length();
    m_paramValues[idx] = (char*)malloc(len + 1);
    m_paramLengths[idx] = len + 1;
    memcpy(m_paramValues[idx], tempstamp.c_str(), len + 1);
    return 0;
}

int PostgresqlStmt::bindNull(int idx) {
    checkParamMemory(idx);
    m_paramValues[idx] = nullptr;
    m_paramLengths[idx] = 0;
    return 0;
}

int PostgresqlStmt::execute() {
    ISQLData::ptr res_ptr = query();
    if(!res_ptr){
        return 0;
    }
    return 1;
}

int64_t PostgresqlStmt::getLastInsertId() {
    return -1;
}

ISQLData::ptr PostgresqlStmt::query() {
    PGresult * res = PQexecParams(m_postgresql->getConn().get(),m_sql.c_str(),m_paramNum
            ,nullptr,&(m_paramValues[0]),&(m_paramLengths[0]),NULL,0);
    if(!res){
        QTCH_LOG_DEBUG(logger) << "postgresql query {" << m_sql << "} faile ," << m_postgresql->getErrStr();
        return nullptr;
    }
    PostgresqlRes::ptr res_ptr = PostgresqlRes::ptr(new PostgresqlRes(res));
    if(res_ptr->getErrno()==PGRES_COMMAND_OK||res_ptr->getErrno()==PGRES_TUPLES_OK){
        return res_ptr; 
    }
    QTCH_LOG_DEBUG(logger) << "PostgresqlStmt query {" << m_sql << "} faile , "<< res_ptr->getErrStr();
    return nullptr;

}

void PostgresqlStmt::setSqlSentence(const std::string& sql) {
    std::stringstream ss;
    size_t begin = 0;
    size_t pos = 0;
    int index = 1;
    while (pos < sql.length()) {
        if (sql[pos] == '?') {
            std::string temp = sql.substr(begin, pos - begin);
            ss << temp;
            ss << "$" << index++;
            pos++;
            begin = pos;
        } else {
            pos++;
        }
    }
    ss << sql.substr(begin);
    m_sql = ss.str();
}

int PostgresqlStmt::getError() {
    return 0;
}

std::string PostgresqlStmt::getErrStr() {
    return "";
}

static PGconn* postgresql_init(const std::map<std::string, std::string>& params){

    std::string host = qtch::GetParamValue<std::string>(params,"host","");
    std::string port = qtch::GetParamValue<std::string>(params,"port","0");
    std::string dbName = qtch::GetParamValue<std::string>(params,"dbName","");
    std::string login = qtch::GetParamValue<std::string>(params,"login","");
    std::string pwd = qtch::GetParamValue<std::string>(params,"pwd","");

    
    PGconn *conn = PQsetdbLogin(host.c_str(),port.c_str(),NULL,NULL,dbName.c_str(),login.c_str(),pwd.c_str());
    if(!conn){
        QTCH_LOG_DEBUG(logger) << "postgresql login faile host=" << host
            << " port=" << port
            << " dbName=" << dbName
            << " login=" << login
            << " pwd=" << "pwd";
        return nullptr;
    }
    QTCH_LOG_DEBUG(logger) << "postgresql login host=" << host
            << " port=" << port
            << " dbName=" << dbName
            << " login=" << login
            << " pwd=" << "pwd";
    ConnStatusType reType = PQstatus(conn);
    if(reType!=CONNECTION_OK){
        QTCH_LOG_DEBUG(logger) << "postgresql login faile rt=" << reType << " errMessage=" << PQerrorMessage(conn);
        PQfinish(conn);
        return nullptr;
    }
    return conn;
}

PostgreSQL::PostgreSQL(const std::string& name,std::map<std::string, std::string>& args){
    m_params = args;
    m_dbname = name;
}

bool PostgreSQL::connect() {
    if(m_conn&&!m_hasError){
        return true;
    }
    PGconn* conn = postgresql_init(m_params);
    if(!conn){
        m_hasError = true;
        return false;
    }
    m_poolSize = TypeUtil::Atoi(qtch::GetParamValue<std::string>(m_params,"pool","5"));
    m_hasError = false;
    m_conn.reset(conn,PQfinish);
    return true;
}

bool PostgreSQL::ping() {
    if(!m_conn){
        return false;
    }
    ConnStatusType reType = PQstatus(m_conn.get());
    if(reType!=CONNECTION_OK){
        m_hasError =  true;
        return false;
    }
    return true;
}

bool PostgreSQL::reConnect() {
    if(!m_conn){
        return false;
    }
    PQreset(m_conn.get());
    ConnStatusType rt = PQstatus(m_conn.get());
    if(rt == CONNECTION_OK){
        return true;
    }
    m_hasError = true;
    return false;

}

bool PostgreSQL::isNeedCheck(){
    time_t now = time(0);
    if(now-m_lastUsedTime < 5){
        return false;
    }
    return true;
}

int64_t PostgreSQL::getLastInsertId(){
    return -1;
}

IStmt::ptr PostgreSQL::prepare(const std::string& stmt)  {
    return PostgresqlStmt::Create(shared_from_this(),stmt);
}


int PostgreSQL::getError()  {
    if(!m_conn){
        return -1;
    }
    return PQstatus(m_conn.get());
}

std::string PostgreSQL::getErrStr()  {
    if(!m_conn){
        return "postgresql is null";
    }
    char * str = PQerrorMessage(m_conn.get());
    if(str){
        return str;
    }
    return "";
}

ITransaction::ptr PostgreSQL::openTransaction(bool auto_commit)  {
    return PostgresqlTransaction::Create(shared_from_this(),auto_commit);
}

ISQLData::ptr PostgreSQL::query(const char* format, ...)  {
    va_list ap;
    va_start(ap,format);
    auto rt=query(format,ap);
    va_end(ap);
    return rt;
}

ISQLData::ptr PostgreSQL::query(const char* format, va_list ap){
    std::string sql = StringUtil::Formatv(format,ap);
    return query(sql);
}

ISQLData::ptr PostgreSQL::query(const std::string& sql)  {
    PGresult * res = PQexec(m_conn.get(),sql.c_str());
    if(!res){
        m_hasError = true;
        return nullptr;
    }
    m_hasError = false;
    ISQLData::ptr rt(new PostgresqlRes(res));
    return rt;

}

int PostgreSQL::execute(const char* format, ...)  {
    va_list ap;
    va_start(ap,format);
    auto rt=execute(format,ap);
    va_end(ap);
    return rt;
}

int PostgreSQL::execute(const char* format, va_list ap) {
    std::string sql = StringUtil::Formatv(format,ap);
    return execute(sql);
}

int PostgreSQL::execute(const std::string& sql)  {
    ISQLData::ptr res = query(sql);
    if(res->getErrno()==PGRES_COMMAND_OK||res->getErrno()==PGRES_TUPLES_OK){
        return 1;
    }
    return 0;
}

PostgresqlTransaction::PostgresqlTransaction(PostgreSQL::ptr pq,bool autoCommit){
    m_autoCommit = autoCommit;
    m_pq = pq;
    m_isfinish = true;
    m_hasError = false;
}

PostgresqlTransaction::~PostgresqlTransaction() {
    if(m_autoCommit){
        commit();
    } else {
        rollback();
    }
}

bool PostgresqlTransaction::begin() {
    if(!m_isfinish){
        QTCH_LOG_ERROR(logger) << "postgresql already in Transaction";
        return false;
    }
    m_isfinish = false;
    int rt = execute("BEGIN;");
    return rt;
}

bool PostgresqlTransaction::commit() {
    if(m_isfinish || m_hasError){
        return !m_hasError;
    }
    int rt = execute("COMMIT;");
    if(!rt){
        m_hasError = true;
    } else{
        m_isfinish = true;
    }
    return rt;
}

bool PostgresqlTransaction::rollback() {
    if(m_isfinish){
        return true;
    }
    int rt = execute("ROLLBACK;");
    if(!rt){
        m_hasError = true;
    } else{
        m_isfinish = true;
    }
    return rt;
}

int PostgresqlTransaction::execute(const char* format, ...) {
    va_list ap;
    va_start(ap,format);
    int rt = execute(format,ap);
    va_end(ap);
    return rt;
}

int PostgresqlTransaction::execute(const char* format, va_list ap) {
    std::string sql = StringUtil::Formatv(format,ap);
    return execute(sql);
}

int PostgresqlTransaction::execute(const std::string& sql) {
    return m_pq->execute(sql);
}

int64_t PostgresqlTransaction::getLastInsertId() {
    return -1;
}

PostgresqlTransaction::ptr PostgresqlTransaction::Create(PostgreSQL::ptr pq,bool autoCommit){
    return PostgresqlTransaction::ptr(new PostgresqlTransaction(pq,autoCommit));
}

PostgresqlManager::PostgresqlManager() {
    m_maxConn = 10;
}

PostgresqlManager::~PostgresqlManager() {
    for(auto & i : m_pqs){
        for(auto& j: i.second){
            delete j;
        }
    }
}

PostgreSQL::ptr PostgresqlManager::get(const std::string& name) {
    MutexType::Lock lock(m_mutex);
    auto list_pq = m_pqs.find(name);
    if(list_pq!=m_pqs.end()){
        if(!list_pq->second.empty()){
            PostgreSQL* pq = list_pq->second.front();
            list_pq->second.pop_front();
            lock.unlock();
            if(!pq->isNeedCheck()){
                pq->m_lastUsedTime = time(0);
                return PostgreSQL::ptr(pq,std::bind(&PostgresqlManager::freePQ,this,name, std::placeholders::_1));
            }
            if(pq->ping()){
                pq->m_lastUsedTime = time(0);
                return PostgreSQL::ptr(pq,std::bind(&PostgresqlManager::freePQ,this,name, std::placeholders::_1));
            } 
            if(pq->reConnect()){
                pq->m_lastUsedTime = time(0);
                return PostgreSQL::ptr(pq,std::bind(&PostgresqlManager::freePQ,this,name, std::placeholders::_1));
            }else{
                QTCH_LOG_ERROR(logger) << "postgresql reconnect " << name << " fail";
                return nullptr;
            }
        }
    }

    auto config = g_postgresql_dbs->getValue();
    auto sit = config.find(name);
    std::map<std::string, std::string> args;
    if(sit != config.end()){
        args = sit->second;
    } else {
        sit = m_defines.find(name);
        if(sit!=m_defines.end()){
            args = sit->second;
        } else {
            QTCH_LOG_ERROR(logger) << "PostgresqlManager not find name=" << name;
            return nullptr;
        }
    }
    lock.unlock();

    PostgreSQL* pq = new PostgreSQL(name,args);
    if(pq->connect()){
        pq->m_lastUsedTime = time(0);
        return PostgreSQL::ptr(pq,std::bind(&PostgresqlManager::freePQ,this,name, std::placeholders::_1));
    } else {
        delete pq;
        QTCH_LOG_ERROR(logger) << "postgersql connect faile name=" << name;
        return nullptr;
    }

}

void PostgresqlManager::registerPostgresql(const std::string& name,const std::map<std::string,std::string>& define) {
    MutexType::Lock lock(m_mutex);
    m_defines[name] = define;
}

void PostgresqlManager::checkConnect(uint64_t sec) {
    time_t now = time(0);
    std::vector<PostgreSQL*> readyDelete;
    {
        MutexType::Lock lock(m_mutex);
        for(auto& i:m_pqs){
            for(auto it = i.second.begin();it!=i.second.end();){
                if(now-(*it)->getLastUsedTime()>sec){
                    auto temp = *it;
                    it = i.second.erase(it);
                    readyDelete.push_back(temp);
                }else{
                    it++;
                }
                
            }
        }
    }
    for(auto& i : readyDelete){
        delete i;
    }


}

ISQLData::ptr PostgresqlManager::query(const std::string& name,const char* format, ...) {
    va_list ap;
    va_start(ap,format);
    ISQLData::ptr rt = query(name,format,ap);
    va_end(ap);
    return rt;
}

ISQLData::ptr PostgresqlManager::query(const std::string& name,const char* format, va_list ap) {
    std::string sql = StringUtil::Formatv(format,ap);
    return query(name,sql);
}

ISQLData::ptr PostgresqlManager::query(const std::string& name,const std::string& sql) {
    PostgreSQL::ptr postgresql_ptr = get(name);
    if(!postgresql_ptr){
        return nullptr;
    }
    return postgresql_ptr->query(sql);
}

int PostgresqlManager::execute(const std::string& name,const char* format, ...) {
    va_list ap;
    va_start(ap,format);
    int rt = execute(name,format,ap);
    va_end(ap);
    return rt;
}

int PostgresqlManager::execute(const std::string& name,const char* format,va_list ap) {
    std::string sql = StringUtil::Formatv(format,ap);
    return execute(name,sql);
}

int PostgresqlManager::execute(const std::string& name,const std::string& sql) {
    PostgreSQL::ptr postgresql_ptr = get(name);
    if(!postgresql_ptr){
        return 0;
    }
    return postgresql_ptr->execute(sql);
}

void PostgresqlManager::freePQ(const std::string& name,PostgreSQL* m) {
    QTCH_LOG_DEBUG(logger) << "freePQ name=" << m->m_dbname;
    if(m->m_conn){
        MutexType::Lock lock(m_mutex);
        if(m_pqs[name].size()<(size_t)m->m_poolSize){
            m_pqs[name].push_back(m);
            return;
        }
    }
    delete m;
}   







}  // namespace qtch

